package com.liuyjy.redis.lock;

import com.liuyjy.core.exception.RedissonLockException;
import com.liuyjy.redis.annotations.RedissonLock;
import com.liuyjy.redis.util.RedissonLockUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.springframework.core.StandardReflectionParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;


/**
 * @Author liuyjy
 * @Date 2025/2/7 12:33
 * @Description: 分布式锁切面 阻塞式锁
 **/
@Slf4j
@Aspect
@Component
@Order(Integer.MIN_VALUE)
public class RedissonLockAspect {

    //    public RedissonLockAspect(RedissonClient redissonClient) {
//        this.redissonClient = redissonClient;
//    }
    @Resource
    private RedissonLockUtil redissonLockUtil;

    @Around("@annotation(com.liuyjy.redis.annotations.RedissonLock)")
    public Object process(ProceedingJoinPoint pjp) {
        Object response = null;
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        RedissonLock distributeLock = method.getAnnotation(RedissonLock.class);

        // 获取锁的key
        String lockKey = getLockKey(pjp, distributeLock, method);

        // 锁的过期时间
        int expireTime = distributeLock.expireTime();

        // 锁的等待时间
        int waitTime = distributeLock.waitTime();

        // 是否是阻塞式锁
        boolean blocking = distributeLock.blocking();

        // 锁的过期时间单位
        TimeUnit timeUnit = distributeLock.timeUnit();

        // 获取不到锁时提示
        String noLockMsg = distributeLock.noLockMsg();

        RLock rLock = redissonLockUtil.getRLock(lockKey);
        try {
            boolean lockResult;
            if (blocking) {
                // 阻塞式锁，如果获取锁失败，则等待指定时间再尝试获取锁
                lockResult = getLock(waitTime, rLock, expireTime, timeUnit);
                log.info("lock for key: {}, expire: {}", RedissonLockUtil.REDISSON_LOCKKEY + lockKey, expireTime);
            } else {
                // 非阻塞式锁，如果获取锁失败，则直接返回
                lockResult = getTryLock(waitTime, rLock, expireTime, timeUnit);
                log.info("try lock for key: {}, expire: {}, wait: {}", RedissonLockUtil.REDISSON_LOCKKEY + lockKey, expireTime, waitTime);
            }
            // 如果获取锁失败，则抛出异常
            if (!lockResult) {
                log.warn("lock failed for key: {}, expire: {}", RedissonLockUtil.REDISSON_LOCKKEY + lockKey, expireTime);
                throw new RedissonLockException(noLockMsg);
            }
            log.info("lock success for key: {}, expire: {}", RedissonLockUtil.REDISSON_LOCKKEY + lockKey, expireTime);
            response = pjp.proceed();
        } catch (Throwable e) {
            log.error("lock failed for key: {}, expire: {}", RedissonLockUtil.REDISSON_LOCKKEY + lockKey, expireTime, e);
            throw new RedissonLockException(e.getMessage());
        } finally {
            if (rLock.isHeldByCurrentThread()) {
                rLock.unlock();
                log.info("unlock for key: {}, expire: {}", RedissonLockUtil.REDISSON_LOCKKEY + lockKey, expireTime);
            }
        }
        return response;
    }

    /**
     * 判断是否获取锁成功
     *
     * @param waitTime       等待时间
     * @param rLock          锁
     * @param expireTime     过期时间
     * @param timeUnit       时间单位
     * @return boolean
     * @throws InterruptedException 线程中断异常
     */
    private boolean getLock(int waitTime, RLock rLock, int expireTime, TimeUnit timeUnit) throws InterruptedException {
        if(waitTime !=RedissonLockConstant.DEFAULT_WAIT_TIME){
            return rLock.tryLock(waitTime, expireTime, timeUnit);
        }
        rLock.lock(expireTime, timeUnit);
        return true;
    }

    /**
     * 判断是否获取锁成功 阻塞式锁
     *
     * @param waitTime       等待时间
     * @param rLock          锁
     * @param expireTime     过期时间
     * @param timeUnit       时间单位
     * @return boolean
     * @throws InterruptedException 线程中断异常
     */
    private boolean getTryLock(int waitTime, RLock rLock, int expireTime, TimeUnit timeUnit) throws InterruptedException {
        if(waitTime !=RedissonLockConstant.DEFAULT_WAIT_TIME){
            return rLock.tryLock(waitTime, expireTime, timeUnit);
        }
        if(expireTime !=RedissonLockConstant.DEFAULT_EXPIRE_TIME){
            return rLock.tryLock(expireTime, timeUnit);
        }
        return rLock.tryLock();
    }

    /**
     * 获取锁的key
     *
     * @param pjp            ProceedingJoinPoint
     * @param distributeLock 锁信息
     * @param method         方法
     * @return String
     */
    private String getLockKey(ProceedingJoinPoint pjp, RedissonLock distributeLock, Method method) {

        String key = distributeLock.key();

        //锁的场景
        String scene = distributeLock.scene();

        //如果key不为空，则直接返回
        if (!RedissonLockConstant.NONE_KEY.equals(key)) {
            return scene + ":" + key;
        }
        //SPEL表达式
        String keyExpression = distributeLock.keyExpression();

        if (RedissonLockConstant.NONE_KEY.equals(keyExpression)) {
            throw new RedissonLockException("no lock key found...");
        }

        SpelExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(keyExpression);

        EvaluationContext context = new StandardEvaluationContext();
        // 获取参数值
        Object[] args = pjp.getArgs();

        // 获取运行时参数的名称
        StandardReflectionParameterNameDiscoverer discoverer
                = new StandardReflectionParameterNameDiscoverer();
        String[] parameterNames = discoverer.getParameterNames(method);

        // 将参数绑定到context中
        if (parameterNames != null) {
            for (int i = 0; i < parameterNames.length; i++) {
                context.setVariable(parameterNames[i], args[i]);
            }
        }

        // 解析表达式，获取结果
        key = String.valueOf(expression.getValue(context));
        return scene + ":" + key;
    }
}
